<template>
  <div
    class="relative"
    ref="containerTarget"
    :style="{
      height: containerHeight + 'px'
    }"
  >
    <!-- 数据渲染 -->
    <template v-if="columnWidth && props.data.length">
      <div
        v-for="(item, index) in props.data"
        :key="props.nodeKey ? item[props.nodeKey] : index"
        class="m-waterfall-item absolute duration-300"
        :style="{
          width: columnWidth + 'px',
          left: item._style?.left + 'px',
          top: item._style?.top + 'px'
        }"
      >
        <slot :item="item" :width="columnWidth" :index="index" />
      </div>
    </template>
    <!-- 加载中 -->
    <div v-else>加载中...</div>
  </div>
</template>

<script setup>
import { increaseWithUnit } from '@vueuse/core'
import {
  ref,
  reactive,
  toRefs,
  onMounted,
  computed,
  watch,
  nextTick,
  onUnmounted
} from 'vue'
import {
  getImgElements,
  getAllImg,
  onComplateImgs,
  getMinHeightColumn,
  getMinHeight,
  getMaxHeight
} from './index.js'

const props = defineProps({
  // 数据源
  data: {
    type: Array,
    required: true
  },
  //   唯一标识
  nodeKey: {
    type: String
  },
  // 列数
  column: {
    type: Number,
    default: 5
  },
  // 列间距
  columnSpacing: {
    type: Number,
    default: 10
  },
  // 行间距
  rowSpacing: {
    type: Number,
    default: 10
  },
  // 是否需要图片预读取
  picturePerReading: {
    type: Boolean,
    default: true
  }
})

// 容器总高度 = 最高那一列的高度
const containerHeight = ref(0)

//  记录每列高度的容器 key 为所在列 val;列高
const columnHeightObj = ref({})

// 构建每列高度的对象
const useColumnHeightObj = () => {
  columnHeightObj.value = {}
  for (let i = 0; i < props.column; i++) {
    columnHeightObj.value[i] = 0
  }
}

// 容器实例
const containerTarget = ref(null)
// 容器的总宽度 不包含 padding margin border
const containerWidth = ref(0)
// 容器的左边距，计算 item 的 left
const containerLeft = ref(0)

// 计算容器的宽度
const useContainerWidth = () => {
  const { paddingLeft, paddingRight } = getComputedStyle(
    containerTarget.value,
    null
  )
  //   获取容器的左边距
  containerLeft.value = parseFloat(paddingLeft)
  // 获取 容器的宽度
  containerWidth.value =
    containerTarget.value.offsetWidth -
    parseFloat(paddingLeft) -
    parseFloat(paddingRight)
}

// 列宽 容器的宽度 -所有的列间距宽度 / 列数
const columnWidth = ref(0)
// 列间距的合计 有几个列间距
const columnSpacingTotal = computed(() => {
  return (props.column - 1) * props.columnSpacing
})

// 计算列宽
const useColumnWidth = () => {
  useContainerWidth()
  columnWidth.value =
    (containerWidth.value - columnSpacingTotal.value) / props.column
}
onMounted(() => {
  useColumnWidth()
})

// item 高度集合
let itemHeights = ref([])
/**
 * 监听图片加载完成  需要图片预加载情况
 */
const waitImgComplate = () => {
  itemHeights.value = []
  // 拿到所有元素
  let itemElements = [...document.getElementsByClassName('m-waterfall-item')]
  // 获取所有元素的 img 标签
  const imgElements = getImgElements(itemElements)
  // 获取所有 img 标签的图片
  const allImgs = getAllImg(imgElements)
  onComplateImgs(allImgs)
    .then((res) => {
      // 图片加载完成，获取高度
      itemElements.forEach((el) => {
        itemHeights.value.push(el.offsetHeight)
      })
    })
    .finally(() => {
      // 渲染位置
      useItemLocation()
    })
}
/**
 * 图片不需要预加载时，计算 item 高度
 */
const useItemHeight = () => {
  itemHeights.value = []
  // 拿到所有元素
  let itemElements = [...document.getElementsByClassName('m-waterfall-item')]
  // 计算 item 高度
  itemElements.forEach((el) => {
    // 依据传入数据计算出的 img 高度
    itemHeights.value.push(el.offsetHeight)
  })
  // 渲染位置
  useItemLocation()
}
// 渲染位置函数
const useItemLocation = () => {
  props.data.forEach((item, index) => {
    // 避免重复计算
    if (item._style) return
    item._style = {}
    item._style.left = getNextItemLeft()
    item._style.top = getNextItemTop()
    // // 指定的列高度自增
    increaseHeight(index)
  })
  // 指定容器的高度
  containerHeight.value = getMaxHeight(columnHeightObj.value)
}
onUnmounted(() => {
  props.data.forEach((item) => {
    delete item._style
  })
  clearTimeout(reset.value)
})
// 指定高度列自增
const increaseHeight = (index) => {
  // 拿到最小高度的列
  const minHeightColumn = getMinHeightColumn(columnHeightObj.value)
  // 让列高度自增
  columnHeightObj.value[minHeightColumn] +=
    itemHeights.value[index] + props.rowSpacing
}
// 返回下一个 item 的 left
const getNextItemLeft = (index) => {
  // 拿到最小的列
  const minColumn = getMinHeightColumn(columnHeightObj.value)
  return (
    minColumn * (columnWidth.value + props.columnSpacing) + containerLeft.value
  )
}
// 返回下一个 item 的 top
const getNextItemTop = (index) => {
  // 拿到最小的列
  return getMinHeight(columnHeightObj.value)
}
// 触发高度计算
watch(
  () => props.data,
  (value) => {
    nextTick(() => {
      // 第一次获取数据时，构建高度记录容器
      const resetColumcHeight = value.every((item) => !item._style)
      if (resetColumcHeight) {
        useColumnHeightObj()
      }
      if (props.picturePerReading) {
        waitImgComplate()
      } else {
        useItemHeight()
      }
    })
  },
  { deep: true, immediated: true }
)
// 计算响应式的列
watch(
  () => props.column,
  (value) => {
    if (props.picturePerReading) {
      columnWidth.value = 0
      resetComputed()
    } else {
      resetComputed()
    }
  }
)
const reset = ref(null)
// 重置所有的定位，重新渲染瀑布流
const resetComputed = () => {
  reset.value = setTimeout(() => {
    // 重新计算列宽
    useColumnWidth()
    props.data.forEach((item) => {
      item._style = null
    })
  }, 200)
}
</script>
<style scoped lang="scss"></style>
